use std::thread::sleep;
use std::time;

use crate::db::db_user::{self};
use crate::db::db_user_role;
use crate::db::entities::user::Model;
use crate::rest_api::api_user::{CreateParams, LoginParams, LoginRsp, RegisterParams, SearchParams, UpdateParams, User, UserToken};
use crate::rest_api::defines::{ADMIN, IamError};
use crate::rest_api::service::auth_check::check_is_admin;
use crate::rest_api::service::srv_token::create_token;
use crate::utils::security_utils::{create_password, validate_password};
use anyhow::Result;
use log::warn;
use rbase::define::{ApiError, BaseError};
use rbase::id_utils::str2id;

pub async fn login(login_param: &LoginParams) -> Result<LoginRsp, ApiError> {
    sleep(time::Duration::from_millis(200));

    let user_model = db_user::find_by_login_name(&login_param.login_name)
        .await?
        .ok_or_else(|| IamError::LoginFailed)?;
    let password = &user_model.password.clone().ok_or_else(|| BaseError::InternalError)?;
    let salt = &user_model.salt.clone().ok_or_else(|| BaseError::InternalError)?;

    match validate_password(&login_param.password, password, salt) {
        true => {
            let mut user = User::from_model(&user_model);
            let roles = db_user_role::find_by_user_id(user_model.id).await?;
            user.roles = Some(roles);
            return Ok(LoginRsp {
                token: create_token(&user)?,
                user,
            });
        }
        false => {
            warn!("Login failed: {}", login_param.login_name);
            return Err(IamError::LoginFailed.into());
        }
    }
}

pub async fn register(register_param: &RegisterParams) -> Result<(), ApiError> {
    sleep(time::Duration::from_millis(200));

    if db_user::find_by_login_name(&register_param.login_name).await?.is_some() {
        return Err(IamError::RegisterFailed.into());
    }

    let (password, salt) = create_password(&register_param.password);

    let new_model = Model {
        name: Some(register_param.name.clone()),
        login_name: Some(register_param.login_name.clone()),
        password: Some(password),
        salt: Some(salt),
        ..Default::default()
    };
    db_user::insert(new_model).await?;
    Ok(())
}

pub async fn find_user_by_id(user_token: &UserToken, user_id_str: &String) -> Result<User, ApiError> {
    let user_id = str2id(user_id_str)?;
    if !user_token.is_admin && user_id != user_token.id {
        return Err(BaseError::PermissionDenied.into());
    }
    let user_model = db_user::find_by_id(user_id)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(user_id_str.to_string()))?;
    Ok(User::from_model(&user_model))
}

pub async fn delete_user_by_id(user_token: &UserToken, user_id: &String) -> Result<(), ApiError> {
    check_is_admin(user_token)?;
    let user_model = db_user::find_by_id(str2id(user_id)?)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(user_id.to_string()))?;
    if user_model.login_name == Some(ADMIN.to_string()) {
        return Err(BaseError::PermissionDenied.into());
    }
    db_user::delete_by_id(user_model.id).await?;
    Ok(())
}

pub async fn update_user_by_id(user_token: &UserToken, user_id_str: &String, new_user: &UpdateParams) -> Result<(), ApiError> {
    let user_id = str2id(user_id_str)?;
    if !user_token.is_admin && user_id != user_token.id {
        return Err(BaseError::PermissionDenied.into());
    }
    let old_model = db_user::find_by_id(user_id)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(user_id_str.to_string()))?;
    if old_model.login_name == Some(ADMIN.to_string()) && new_user.login_name != ADMIN.to_string() {
        return Err(BaseError::PermissionDenied.into());
    }
    let new_model = Model {
        id: old_model.id,
        name: Some(new_user.name.clone()),
        login_name: Some(new_user.login_name.clone()),
        ..Default::default()
    };
    db_user::update(new_model).await?;
    Ok(())
}

pub async fn create_user(_user_token: &UserToken, new_user: &CreateParams) -> Result<User> {
    let (password, salt) = create_password(&new_user.login_name);
    let new_model = Model {
        name: Some(new_user.name.clone()),
        login_name: Some(new_user.login_name.clone()),
        password: Some(password),
        salt: Some(salt),
        ..Default::default()
    };
    let created_model = db_user::insert(new_model).await?;
    Ok(User::from_model(&created_model))
}

pub async fn search(user_token: &UserToken, user_search_params: &SearchParams) -> Result<(Vec<User>, i64), ApiError> {
    check_is_admin(user_token)?;
    let (users, total) = db_user::search(&db_user::SearchParams {
        name: user_search_params.name.clone(),
        offset: user_search_params.offset,
        limit: user_search_params.limit,
        order_by: user_search_params.order_by.clone(),
        order: user_search_params.order.clone(),
    })
    .await?;
    Ok((users.into_iter().map(|user| User::from_model(&user)).collect(), total))
}
